package com.lsh.spring;

import com.lsh.annotation.Autowired;
import com.lsh.annotation.Component;
import com.lsh.annotation.ComponentScan;
import com.lsh.annotation.Scope;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

public class LshApplicationContext {
    private Class configClass;
    //保存beanDefinition的map
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    //保存单例bean的map
    private ConcurrentHashMap<String, Object> singletonObjectMap = new ConcurrentHashMap<>();
    //
    private ArrayList<BeanPostProcessor> beanPostProcessorsList = new ArrayList<>();


    public LshApplicationContext(Class configClass) {
        this.configClass = configClass;
        // 扫描
        // 先判断是否带有ComponentScan注解
        // 从配置类开始加载，所以从配置类开始判断
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan annotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String path = annotation.value(); // 扫描路径,要扫描编译之后的文件 com.lsh.service

            //获取注解的包路径转换成相对路径
            path = path.replace(".", "/"); // com/lsh/service

            //通过类加载器获取绝对路径
            ClassLoader classLoader = LshApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);

            //获取文件夹
            File file = new File(resource.getFile());

            //System.out.println(file);
            //如果是文件夹
            if (file.isDirectory()) {
                //获取文件夹下的所有文件
                File[] files = file.listFiles();

                //遍历所有文件
                for (File f : files) {
                    //获取绝对路径  D:\study\developPlace\idea\LshSpring\src\com\lsh\service\UserService.java
                    String fileName = f.getAbsolutePath();
                    //System.out.println(fileName);
                    if (fileName.endsWith(".class")){
                        //如果以.class结尾截取路径从com到.class
                        String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                        className = className.replace("\\", ".");
                        //使用反射获取类的注解，需要类的全限定名，使用类加载器获取
                        try {
                            //System.out.println(className);
                            //获取bean的 《类对象》
                            Class<?> clazz = classLoader.loadClass(className);
                            //如果存在Component注解
                            if (clazz.isAnnotationPresent(Component.class)) {

                                //判断clazz是否由BeanPostProcessor派生的，继承实现
                                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                    BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
                                    beanPostProcessorsList.add(instance);
                                }

                                //获取bean的名字
                                String beanName = clazz.getAnnotation(Component.class).value();
                                //Component注解中不写类名时创建默认名字
                                if (beanName.equals("")){
                                    beanName = Introspector.decapitalize(clazz.getSimpleName());
                                }
                                //生成BeanDefinition 保存到map  键:bean的名字  值:BeanDefinition(bean的单例多例类型以及bean的类对象)
                                BeanDefinition definition = new BeanDefinition();
                                definition.setType(clazz);

                                //判断类是否带有Scope注解，有的话看是多例bean
                                //单例bean  在容器中只创建一次
                                //多例bean  每次调用都创建一次
                                if (clazz.isAnnotationPresent(Scope.class)) {
                                    //获取Scope注解里的值
                                    String value = clazz.getAnnotation(Scope.class).value();
                                    definition.setScope(value);
                                } else {//没有Scope注解是单例bean
                                    definition.setScope("singleton");
                                }

                                beanDefinitionMap.put(beanName, definition);

                            }
                        } catch (ClassNotFoundException e) {
                            throw new RuntimeException(e);
                        } catch (InstantiationException e) {
                            throw new RuntimeException(e);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }

        //扫描完成之后开始判断单例bean直接创建到容器(singletonObjectMap)当中
        //创建单例bean
        for (String beanName : beanDefinitionMap.keySet()) {
            //获取对应的beanDefinition对象
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断是单例bean的直接创建
            if ("singleton".equals(beanDefinition.getScope())){
                Object bean = createBean(beanName, beanDefinition);
                singletonObjectMap.put(beanName, bean);
            }
        }

    }

    public Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class clazz = beanDefinition.getType();

        //使用反射调用无参构造函数
        try {
            //构造器创建对象
            Object instance = clazz.getConstructor().newInstance();

            //在初始化的时候就把依赖注入 为什么先根据类型，再根据名字
            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(Autowired.class)){
                    f.setAccessible(true);
                    //这就是为什么getBean为空时先去创建，先创建UserService时OrderService还没有被创建
                    //调用getBean时自动就创建了，涉及到循环依赖注入问题
                    f.set(instance, getBean(f.getName()));
                }
            }

            //依赖注入完之后
            //判断是否实现了BeanNameAware接口
            // Aware 回调 会给一个值
            if (instance instanceof BeanNameAware){
                ((BeanNameAware)instance).setBeanName(beanName);
            }

            // BeanPostProcessor AOP
            // BeanPostProcessor 方便程序员以更加灵活的方式干预bean的创建过程


            // 初始化之前，处理所有的实现BeanPostProcessor的类的方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorsList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
            }

            // 初始化方法
            if (instance instanceof InitializingBean){
                ((InitializingBean)instance).afterPropertiesSet();
            }

            // 初始化之后，处理所有的实现BeanPostProcessor的类的方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorsList) {
                instance = beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }

            return instance;

        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

    }

    //获取bean的方法
    public Object getBean(String beanName){
        // beanName
        // 根据名字先获取beanDefinition
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null){
            //为空抛出异常
            throw new NullPointerException();
        } else {
            //获取beanDefinition的scope属性判断是单例还是多例
            String scope = beanDefinition.getScope();
            if ("singleton".equals(scope)){
                //单例的话
                //直接先从单例池中拿
                Object bean = singletonObjectMap.get(beanName);
                //没有就创建
                if (bean == null){
                    bean = createBean(beanName, beanDefinition);
                    //创建好后再放入单例池
                    singletonObjectMap.put(beanName, bean);
                }
                return bean;
            } else {
                //多例就直接创建
                return createBean(beanName, beanDefinition);
            }
        }
    }
}
